Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 4a1832ae3423f1e7c88be39831db81cab19bdcfe


Parents : 8ad19cf
Author : edd <>
Date : 2024-08-17T01:41:51+03:00

Add Windows log static tail

Changes

1 files changed, 87 insertions(+), 9 deletions(-)


Diff

diff --git a/nomadnet/ui/textui/Log.py b/nomadnet/ui/textui/Log.py
index c09855d..9156e4e 100644
--- a/nomadnet/ui/textui/Log.py
+++ b/nomadnet/ui/textui/Log.py
@@ -1,6 +1,11 @@
+import os
+import sys
+import itertools
+import mmap
import urwid
import nomadnet
+
class LogDisplayShortcuts():
def __init__(self, app):
import urwid
@@ -8,28 +13,31 @@ class LogDisplayShortcuts():
self.widget = urwid.AttrMap(urwid.Text(""), "shortcutbar")
+
class LogDisplay():
def __init__(self, app):
self.app = app
- self.log_term = None
self.shortcuts_display = LogDisplayShortcuts(self.app)
self.widget = None
+ @property
+ def log_term(self):
+ return self.widget
+
def show(self):
- if self.log_term == None:
- self.log_term = LogTerminal(self.app)
- self.widget = urwid.LineBox(self.log_term)
+ if self.widget is None:
+ self.widget = log_widget(self.app)
def kill(self):
- if self.log_term != None:
- self.log_term.terminate()
- self.log_term = None
+ if self.widget is not None:
+ self.widget.terminate()
self.widget = None
def shortcuts(self):
return self.shortcuts_display
+
class LogTerminal(urwid.WidgetWrap):
def __init__(self, app):
self.app = app
@@ -39,7 +47,8 @@ class LogTerminal(urwid.WidgetWrap):
escape_sequence="up",
main_loop=self.app.ui.loop,
)
- super().__init__(self.log_term)
+ self.widget = urwid.LineBox(self.log_term)
+ super().__init__(self.widget)
def terminate(self):
self.log_term.terminate()
@@ -49,4 +58,73 @@ class LogTerminal(urwid.WidgetWrap):
if key == "up":
nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.focus_position = "header"
- return super(LogTerminal, self).keypress(size, key)
\ No newline at end of file
+ return super(LogTerminal, self).keypress(size, key)
+
+
+class LogTail(urwid.WidgetWrap):
+ def __init__(self, app):
+ self.app = app
+ self.log_tail = urwid.Text(tail(self.app.logfilepath, 50))
+ self.log = urwid.Scrollable(self.log_tail)
+ self.log.set_scrollpos(-1)
+ self.log_scrollbar = urwid.ScrollBar(self.log)
+ # We have this here because ui.textui.Main depends on this field to kill it
+ self.log_term = None
+
+ super().__init__(self.log_scrollbar)
+
+ def terminate(self):
+ pass
+
+
+def log_widget(app, platform=sys.platform):
+ if platform == "linux":
+ return LogTerminal(app)
+ elif platform == "win32":
+ return LogTail(app)
+ else:
+ raise ValueError(f"Unknown platform {platform}. Expected linux/win32")
+
+
+# https://stackoverflow.com/a/34029605/3713120
+def _tail(f_name, n, offset=0):
+ def skip_back_lines(mm: mmap.mmap, numlines: int, startidx: int) -> int:
+ '''Factored out to simplify handling of n and offset'''
+ for _ in itertools.repeat(None, numlines):
+ startidx = mm.rfind(b'\n', 0, startidx)
+ if startidx < 0:
+ break
+ return startidx
+
+ # Open file in binary mode
+ with open(f_name, 'rb') as binf, mmap.mmap(binf.fileno(), 0, access=mmap.ACCESS_READ) as mm:
+ # len(mm) - 1 handles files ending w/newline by getting the prior line
+ startofline = skip_back_lines(mm, offset, len(mm) - 1)
+ if startofline < 0:
+ return [] # Offset lines consumed whole file, nothing to return
+ # If using a generator function (yield-ing, see below),
+ # this should be a plain return, no empty list
+
+ endoflines = startofline + 1 # Slice end to omit offset lines
+
+ # Find start of lines to capture (add 1 to move from newline to beginning of following line)
+ startofline = skip_back_lines(mm, n, startofline) + 1
+
+ # Passing True to splitlines makes it return the list of lines without
+ # removing the trailing newline (if any), so list mimics f.readlines()
+ # return mm[startofline:endoflines].splitlines(True)
+ # If Windows style \r\n newlines need to be normalized to \n
+ return mm[startofline:endoflines].replace(os.linesep.encode(sys.getdefaultencoding()), b'\n').splitlines(True)
+
+
+def tail(f_name, n):
+ """
+ Return the last n lines of a given file name, f_name.
+ Akin to `tail -<n> <f_name>`
+ """
+ def decode(b):
+ return b.decode(encoding)
+
+ encoding = sys.getdefaultencoding()
+ lines = map(decode, _tail(f_name=f_name, n=n))
+ return ''.join(lines)


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────